package com.wsoft.bpm.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wsoft.bpm.bean.ActModelMetaInfo;
import com.wsoft.bpm.dto.ActModelDTO;
import com.wsoft.bpm.dto.ActModelDataDTO;
import com.wsoft.bpm.entity.ActReModelEntity;
import com.wsoft.bpm.entity.ActWorkflowConfigEntity;
import com.wsoft.bpm.enums.DynamicApprovalType;
import com.wsoft.bpm.enums.ExecuteCategory;
import com.wsoft.bpm.mapper.ActReModelMapper;
import com.wsoft.bpm.query.model.ActModelQuery;
import com.wsoft.bpm.query.node.DefaultBaseNode;
import com.wsoft.bpm.query.node.TaskQuery;
import com.wsoft.bpm.service.IActReModelService;
import com.wsoft.bpm.service.IActWorkflowConfigService;
import com.wsoft.bpm.utils.ActivityUtils;
import com.wsoft.constant.CommonConstant;
import com.wsoft.constant.redis.RedisKeyConstant;
import com.wsoft.core.exception.Asserts;
import com.wsoft.core.service.RedisService;
import com.wsoft.entity.BaseEntity;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.Model;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Author zhonglj
 * @Date 2024-08-28 13:02
 */
@Service
@Log4j2
public class ActReModelServiceImpl extends ServiceImpl<ActReModelMapper, ActReModelEntity> implements IActReModelService {
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private RedisService redisService;
    @Resource
    private IActWorkflowConfigService actWorkflowConfigService;

    @Override
    public String deploy(String modelId) {
        try {
            Model model = repositoryService.getModel(modelId);
            byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
            String processName = model.getName() + ".bpmn20.xml";
            //部署名称
            DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().name(model.getName());
            //完成部署
            String deployXml = ActivityUtils.formatXml(new String(bpmnBytes, StandardCharsets.UTF_8));
            Deployment deployment = deploymentBuilder.addString(processName, deployXml).deploy();
            String deploymentId = deployment.getId();
            // 更新json配置表
            actWorkflowConfigService.updateAfterDeploy(modelId, deploymentId);
            return deploymentId;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            Asserts.fail("[bpm.model.error.deploy.fail]" + e.getMessage());
        }
        return null;
    }

    @Override
    public IPage<ActModelDTO> pageList(ActModelQuery reqVO) {
        Page<ActReModelEntity> page = page(new Page<>(reqVO.getPageNum(), reqVO.getPageSize()),
                Wrappers.lambdaQuery(ActReModelEntity.class)
                        .like(StrUtil.isNotBlank(reqVO.getKey()), ActReModelEntity::getKey, reqVO.getKey())
                        .like(StrUtil.isNotBlank(reqVO.getName()), ActReModelEntity::getName, reqVO.getName())
                        .func(reqVO.getBeginTime() != null, q -> q.gt(ActReModelEntity::getCreateTime, DateUtil.beginOfDay(reqVO.getBeginTime())))
                        .func(reqVO.getEndTime() != null, q -> q.lt(ActReModelEntity::getCreateTime, DateUtil.endOfDay(reqVO.getEndTime())))
                        .orderByDesc(ActReModelEntity::getId)
        );

        return page.convert(e -> {
            ActModelDTO dto = new ActModelDTO();
            dto.setId(e.getId());
            dto.setName(e.getName());
            dto.setKey(e.getKey());
            dto.setCategory(e.getCategory());
            String description = JSONUtil.parseObj(e.getMetaInfo()).getStr("description");
            dto.setDesc(description);
            dto.setVersion(e.getVersion());
            dto.setCreateTime(DateUtil.date(e.getCreateTime()));
            return dto;
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveModelData(ActModelDataDTO modelDTO) {
        String modelId = modelDTO.getModelId();
        byte[] bytes = modelDTO.getData().getBytes(StandardCharsets.UTF_8);
        Model model = repositoryService.getModel(modelId);

        //保存页面配置的json数据
        String key = StrUtil.format(RedisKeyConstant.MODEL_DATA_CACHE_KEY_FIND_BY_MODEL_ID, modelId);
        List<Object> objects = redisService.getByKeyPattern(key);
        if (CollUtil.isNotEmpty(objects)) {
            // 检查缓存是否都通过校验
            Optional<DefaultBaseNode> notValidate = objects.stream()
                    .map(e -> JSONUtil.toBean(JSONUtil.parseObj(e), DefaultBaseNode.class))
                    .filter(node -> !node.isValidated())
                    .findFirst();
            if (notValidate.isPresent()) {
                Asserts.fail("[bpm.model.save.valid]");
            }
        }

        try {
            InputStream stream = new ByteArrayInputStream(bytes);
            XMLInputFactory xif = XMLInputFactory.newInstance();
            InputStreamReader in = new InputStreamReader(stream, StandardCharsets.UTF_8);
            XMLStreamReader xtr = xif.createXMLStreamReader(in);
            BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
            //必要的校验
            checkBpmnModel(bpmnModel, modelId);

            // 检查processId和modelKey是否一致
            String mainProcessId = bpmnModel.getMainProcess().getId();
            if (!StrUtil.equals(model.getKey(), mainProcessId)) {
                // 替换processId
                bytes = updateModelXml(modelDTO.getData(), model.getKey(), null).getBytes(StandardCharsets.UTF_8);
                // 更新缓存的key值
                if (CollUtil.isNotEmpty(objects)) {
                    for (int i = 0; i < objects.size(); i++) {
                        String cache = (String) objects.get(i);
                        cache = cache.replaceAll(mainProcessId, model.getKey());
                        objects.set(i, cache);
                    }
                }
            }
            //保存模型
            repositoryService.saveModel(model);
            //保存资源
            repositoryService.addModelEditorSource(modelId, bytes);

            // 当前模型未删除、未部署的json配置
            List<ActWorkflowConfigEntity> dbExist = actWorkflowConfigService.list(Wrappers.lambdaQuery(ActWorkflowConfigEntity.class)
                    .eq(ActWorkflowConfigEntity::getModelId, modelId)
                    .eq(BaseEntity::getDeleted, CommonConstant.YESNO_NO)
                    .isNull(ActWorkflowConfigEntity::getProcDefId)
            );

            if (dbExist == null) {
                dbExist = new ArrayList<>();
            }

            Map<String, ActWorkflowConfigEntity> dbMap = dbExist.stream()
                    .collect(Collectors.toMap(
                            update -> update.getActId(),
                            update -> update
                    ));

            List<ActWorkflowConfigEntity> updateList = objects.stream()
                    .map(cache -> JSONUtil.parseObj(cache))
                    // 数据库存在
                    .filter(cache -> dbMap.get(cache.getStr("id")) != null)
                    .map(cache -> {
                        ActWorkflowConfigEntity exist = dbMap.get(cache.getStr("id"));
                        exist.setJson(JSONUtil.toJsonStr(cache));
                        return exist;
                    }).collect(Collectors.toList());
            actWorkflowConfigService.updateBatchById(updateList);

            List<ActWorkflowConfigEntity> insertList = objects.stream()
                    .map(cache -> JSONUtil.parseObj(cache))
                    // 数据不库存在
                    .filter(cache -> dbMap.get(cache.getStr("id")) == null)
                    .map(cache -> {
                        ActWorkflowConfigEntity entity = new ActWorkflowConfigEntity();
                        entity.setModelId(modelId);
                        entity.setActId(cache.getStr("id"));
                        entity.setJson(JSONUtil.toJsonStr(cache));
                        return entity;
                    }).collect(Collectors.toList());
            actWorkflowConfigService.saveBatch(insertList);

            // 成功保存数据库之后清空缓存
            redisService.deleteByKeyPattern(key);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            Asserts.fail(e.getMessage());
        }
    }

    private void checkBpmnModel(BpmnModel bpmnModel, String modelId) {
        if (bpmnModel == null) {
            Asserts.fail("[bpm.model.check.empty]");
        }

        // 数据库json配置
        Map<String, ActWorkflowConfigEntity> dbMap = actWorkflowConfigService.list(
                Wrappers.lambdaQuery(ActWorkflowConfigEntity.class)
                        .eq(ActWorkflowConfigEntity::getModelId, modelId)
                        .isNull(ActWorkflowConfigEntity::getProcDefId)
        ).stream().collect(Collectors.toMap(ActWorkflowConfigEntity::getActId, Function.identity()));

        // 遍历获取task
        Process process = bpmnModel.getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();
        // 遍历所有流程元素，找出任务节点
        for (FlowElement curFlowElement : flowElements) {
            // 获取json配置
            String key = StrUtil.format(RedisKeyConstant.MODEL_DATA_CACHE_KEY_TEMPLATE, modelId, curFlowElement.getId());
            Object configJson = redisService.get(key);
            if (configJson == null) {
                // 数据库获取
                ActWorkflowConfigEntity config = dbMap.get(curFlowElement.getId());
                if (config != null && StrUtil.isNotBlank(config.getJson())) {
                    configJson = config.getJson();
                }
            }

            // 校验模版中动态上级
            checkDynamicLeader(curFlowElement, configJson);
        }

    }

    /**
     * 动态上级检查
     *
     * @param flowElement
     * @param configJson
     * @return void
     * @author zhonglj
     * @date 2024/12/10 9:05
     **/
    private void checkDynamicLeader(FlowElement flowElement, Object configJson) {
        if (configJson == null) {
            return;
        }

        if (flowElement instanceof UserTask) {
            TaskQuery taskQuery = JSONUtil.toBean(configJson.toString(), TaskQuery.class);
            String executeCategory = taskQuery.getExecuteCategory();
            // 动态上级检查
            if (StrUtil.equals(executeCategory, ExecuteCategory.DYNAMIC.getValue())) {
                String dynamicApprovalType = taskQuery.getDynamicApprovalType();
                String dynamicApprovalValue = taskQuery.getDynamicApprovalValue();

                boolean notOptional = !StrUtil.equals(DynamicApprovalType.OPTIONAL_PERSONNEL.getValue(), dynamicApprovalType);
                boolean blank = StrUtil.isBlank(dynamicApprovalType) || StrUtil.isBlank(dynamicApprovalValue);
                if (blank && notOptional) {
                    Asserts.fail("[bpm.dynamic.leader.empty]");
                }
                if (StrUtil.equals(dynamicApprovalType, DynamicApprovalType.DEPARTMENT_HEAD.getValue())) {
                    // 部门负责人不需要检查
                    return;
                }
                // 查找上一个用户节点
                List<FlowElement> userTasks = ActivityUtils.getPreFirstFlowElement(flowElement, UserTask.class, StartEvent.class);
                // 进行参数校验
                if (userTasks.size() == 0) {
                    Asserts.fail("[bpm.process.error.checkDynamicLeader.size.zero]");
                } else if (userTasks.size() >= 2) {
                    Asserts.fail("[bpm.process.error.checkDynamicLeader.size.two]");
                } else {
                    FlowElement preFlowElement = userTasks.get(0);
                    if (preFlowElement instanceof UserTask) {
                        // 不能是多实例节点
                        if (((UserTask) preFlowElement).hasMultiInstanceLoopCharacteristics()) {
                            Asserts.fail("[bpm.process.error.checkDynamicLeader.preNode.multiInstance]");
                        }
                    }
                }
            }

        }
    }

    private int getMaxVersionFromDatabase(String key) {
        List<Model> models = repositoryService.createModelQuery().modelKey(key).list();
        if (CollUtil.isNotEmpty(models)) {
            // 根据模型version排序
            models = models.stream()
                    .sorted(Comparator.comparingInt(Model::getVersion).reversed())
                    .collect(Collectors.toList());

            return models.get(0).getVersion();
        }

        return 0;
    }

    @Override
    @SneakyThrows
    public String createModel(ActModelDTO modelDTO) {
        String modelName = modelDTO.getName();
        String key = modelDTO.getKey();
        if (!ReUtil.isMatch("^[a-zA-Z].*", key)) {
            Asserts.fail("流程标识必须以首字母开头!");
        }
        String category = modelDTO.getCategory();
        String desc = modelDTO.getDesc();

        //初始化一个空模型
        Model model = repositoryService.newModel();
        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, desc);
        //模型分类
        model.setCategory(category);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());
        //模型名称
        model.setName(modelName);
        // 设置模型版本
        int maxVersion = getMaxVersionFromDatabase(key);
        model.setVersion(maxVersion + 1);

        repositoryService.saveModel(model);
        return model.getId();
    }

    @Override
    public String xmlView(String modelId) {
        try {
            Model modelData = repositoryService.getModel(modelId);
            byte[] bpmnBytes = repositoryService.getModelEditorSource(modelData.getId());
            if (bpmnBytes != null) {
                return new String(bpmnBytes, StandardCharsets.UTF_8);
            } else {
                return "";
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            Asserts.fail("[bpm.model.error.view.fail]");
        }

        return null;
    }

    @Override
    public void deleteModel(String modelId) {
        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
        if (model == null) {
            return;
        }

        repositoryService.deleteModel(modelId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateModel(ActModelDTO modelDTO) {
        String modelId = modelDTO.getId();
        String modelName = modelDTO.getName();
        String key = modelDTO.getKey();
        String category = modelDTO.getCategory();
        String desc = modelDTO.getDesc();

        if (!ReUtil.isMatch("^[a-zA-Z].*", key)) {
            Asserts.fail("流程标识必须以首字母开头!");
        }

        // 查询模版
        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
        String oldKey = model.getKey();
        String oldName = model.getName();
        if (model == null) {
            Asserts.fail("[bpm.model.error.nonexistent]");
        }

        // 更新MetaInfo
        ActModelMetaInfo metaInfo = JSONUtil.toBean(model.getMetaInfo(), ActModelMetaInfo.class);
        metaInfo.setDescription(desc);
        model.setMetaInfo(JSONUtil.toJsonStr(metaInfo));

        // 字段更新
        model.setCategory(category);
        model.setKey(key);
        model.setName(modelName);
        repositoryService.saveModel(model);

        // 同步更新流程配置
        ActModelDataDTO data = new ActModelDataDTO();
        data.setModelId(modelId);
        // 获取当前模型最新流程设计xml
        String newModelXml = updateModelXml(xmlView(modelId),
                !StrUtil.equals(oldKey, key) ? key : null,
                !StrUtil.equals(oldName, modelName) ? modelName : null
        );
        data.setData(newModelXml);
        saveModelData(data);
    }

    @Override
    public String updateModelXml(String xml, String modelKey, String modelName) {
        if (StrUtil.isBlank(xml)) {
            return xml;
        }

        try {
            // 解析XML并绑定命名空间
            SAXReader reader = new SAXReader();
            Document document = reader.read(new StringReader(xml));

            // 定义命名空间映射
            Map<String, String> nsMap = new HashMap<>();
            nsMap.put("bpmn", "http://www.omg.org/spec/BPMN/20100524/MODEL");
            nsMap.put("bpmndi", "http://www.omg.org/spec/BPMN/20100524/DI");
            document.getRootElement().add(new Namespace("bpmn", nsMap.get("bpmn")));
            document.getRootElement().add(new Namespace("bpmndi", nsMap.get("bpmndi")));

            // 修改process-id
            Element process = (Element) document.selectSingleNode("//bpmn:process");
            if (process != null) {
                if (StrUtil.isNotBlank(modelKey)) {
                    // 修改id
                    process.addAttribute("id", modelKey);

                    // 修改BPMNPlane的bpmnElement
                    Element bpmnPlane = (Element) document.selectSingleNode("//bpmndi:BPMNPlane");
                    if (bpmnPlane != null) {
                        // 同步更新
                        bpmnPlane.addAttribute("bpmnElement", modelKey);
                    }
                }

                if (StrUtil.isNotBlank(modelName)) {
                    // 修改name
                    process.addAttribute("name", modelName);
                }

            }

            // 输出修改后的XML
            OutputFormat format = OutputFormat.createPrettyPrint();
            format.setEncoding("UTF-8");
            // 保留XML声明
            format.setSuppressDeclaration(false);

            StringWriter writer = new StringWriter();
            XMLWriter xmlWriter = new XMLWriter(writer, format);
            xmlWriter.write(document);

            return ActivityUtils.formatXml(writer.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return xml;
    }

}
